IO 攻击除了利用 stdin 来getshell,还可以利用 stdout 来泄露 libc 地址。然后本文主要学习一下这种泄露方式。
原理
puts 函数在源码中是由 _IO_puts
实现,而 _IO_puts
函数内部会调用 _IO_sputn
,结果会执行 _IO_new_file_xsputn
,最终会执行 _IO_overflow
1 | int |
我们的目标是 成功进入 _IO_do_write
函数,而 _IO_wirte_base
是我们要修改的目标。
为了不进入第一个 if 分支,这里 f->_flag & _IO_NO_WRITES
的值应该为 0。
同时使 f->_flag & _IO_CURRENTLY_PUTTING
的值为1,防止进入第二个分支。
_IO_do_write
函数的参数为:stdout 结构体、_IO_write_base
和 size(由 f->_IO_wirte_ptr - f->_IO_write_base
产生),而 _IO_do_write
实际会调用 new_do_write, 其参数一致。
1 | static |
_IO_SYSWRITE
就是最重要调用的函数,可理解为 write(fp, data, to_do)
_IO_SYSSEEK
只是简单的调用 lseek,但是不能完全控制 fp->_IO_write_base - fp->_IO_read_end
的值,如果 fp-> _IO_read_end
的值设为 0,那么_IO_SYSSEEK
的第二个参数就会过大,如果设置 fp->_IO_write_base = fp->_IO_read_end
的话,会在其他函数中产生错误,因为fp->_IO_write_base 不能大于 fp->_IO_write_end
。所以这里要设置 fp->_flags | _IO_IS_APPENDING
,避免进入 else if 分支。
最终需要构造的 fp->flags
是这样,才能绕过上面提到的分支。
1 | _flags = 0xfbad0000 |
所以,通常将 stdout 的 flags 修改成 0xfbad18000,将 _IO_write_base
改小,就可以在成 Libc 的泄露。
De1CTF weapon
程序分析
程序存在一个 UAF漏洞,没有输出函数。申请的堆块大小只能为 0到 0x60之间。
同时保护全开,不能够直接覆盖 got表 去 getshell。这里就需要考虑使用 stdout 来泄露 Libc地址。
利用分析
由于这里开启了PIE,我们想要修改 IO_FILE_stdout 结构体是不知道位置的。但是这里可以利用 unsortedbin 的 fd 和 bk 会存储的 main_arena+88 的地址,这个地址与 _IO_FILE_stdout 的地址相差不大,我们可以爆破尝试修改。将 Main_arena+88 的后两字节修改为 _IO_FILE_stdout 前符合一个 堆块 size 的地址,这里选用的 size 为 0x7f(该地址在内存比较常见),然后在 _IO_FILE_stdout 上分配 fake chunk。
然后修改 _IO_FILE_stdout 来输出 libc 地址。
最后修改 malloc_hook 来 getshell。
*重点:在于首先如何构造一个 unsortedbin *
可以利用 UAF 漏洞的 chunk overlaping 来修改后一个 chunk 的 size,使得其 size 大于 fastbin 的范围,然后释放该 chunk 即可将其放入 unsortedbin 中。
然后利用 UAF 漏洞,修改 该chunk 的 bk指针 ,使我们的chunk 分配到 _IO_FILE_stdout 结构体上。
EXP
1.伪造 unsortedbin chunk
首先为了如果要释放 fast chunk 到 unsrotedbin 中,我们需要有一个堆溢出,能修改 chunk size 大于 0x90。
我们可以首先通过 释放两个 fastchunk,然后第二次 free chunk 的 fd 指针的最后一位为 0x10,那么就可以 实现一个 伪造fastbin到 0x10地址处。
然后刚好伪造的堆块,可以修改 下一个堆块的头部,实现了 修改 chunk size。
如下图第2次 红框所示。
然后,这里需要注意由于我们只能申请 最大0x60 大小的 堆块,所以我们需要将该 堆块释放到 fastbin 中。然后再修改该堆块的chunk size为 0x91,然后再次释放该堆块,将该堆块释放到 unsortedbin中。
如下图第3 和 第4次红框所示。
此时该堆块的 fd 和 bk 指针就已经全是 main_arena+88 的地址,然后我们在修改 fd中的后两位为 ‘\xdd\x25’,就有可能实现伪造堆块到 stdout 结构体。
注意,当我们将 chunk size改为 0x91时,需要保证 其后一个堆块的头部,刚好为当前堆头-0x90的地址。所以我们再申请后一个堆块时,先要保证其往下偏移 0x20 的地址是我们伪造的 一个 chunk 头。
如下图第一个红框所示。
2.修改stdout结构体
首先第一步需要修改 IO_2_1_stdout 结构体,我们利用 fake unsortedbin chunk来修改。
在 第一步中,我们伪造了一个 堆块 ,其后两位地址是 \xdd\25, 但是其倒数第3位地址由于是随机的,所以我们总共有 1/16 的机会 将我们伪造的堆块刚好释放到 stdout 结构体中。
如果一旦申请成功,就需要按照上述的 修改 stdout结构体,泄露 libc 地址。修改 flag 为 0xfbad18000 , 将 _IO_write_base
改小 为 末尾一位 为 \x00 ,将 其改小。
从下图可以看到 stdout结构体中: _IO_write_base
的地址是 0x7f2899002600
我们泄露的数据如下,可以看到其中含有很多的 Libc 地址。我们减去偏移,即可得 libc 基址。
3.修改 malloc_hook
泄露了 Libc 地址,我们就能够得到 malloc_hook 的地址了,然后我们就可以伪造堆块释放到 malloc_hook 之前,然后修改 malloc_hook 为 one_gadget
首先我们选择 malloc_hook 前面一个 0x7f 的地址作为 fake chunk size,我们可以看到下图中在 malloc_hook - 0x23 的位置有一个 0x7f,我们可以选择这里
然后就是修改 fastbin chunk 的 fd 指针只想这里,再分配就行:
4.getshell
最后,只需要再执行堆分配函数,就可以getshell
1 | from pwn import * |
数字经济云 fkroman
程序分析
存在一个 UAF漏洞,同时程序开启了所有保护。
Alloc时对 size 没有限制。
而且,edit 时可以输入新的 size,此处存在堆溢出漏洞。
利用分析
- 首先利用UAF漏洞,实现伪造堆块到 stdout 结构体处
这里由于开启 了 PIE,没法直接获取到 stdout 的结构体的地址。仍然使用 main_arena+88 这个地址,将后两位修改为 \x25\xdd。有 1/16的机会 碰撞到stdout 结构体。
所以首先我们需要先 分配一个 大于 fastbin 的块,然后再切割该块,则该块和其剩下块的 fd 和 bk 指针都会存储 main_arena 数组内的地址。我们随后 释放该块,将该块 fd 指针的 后两位修改为 \x25\xdd:
1 | #申请一个unsortedbin chunk |
然后我们不能直接 释放这个 fd 被修改的指针,因为释放后其 fd 指针会被清空。我们可以使用另一个被释放的块 修改其 fd 指针使其 指向 我们修改后的 chunk,这样就可以构造整个 fastbin链条:
1 | chunk1: |
1 | delete(1) |
2.随后修改 stdout 结构体
1 | payload = 'a'*0x33 + p64(0xfbad1800) + p64(0)*3 + '\x00' |
3.最终修改 malloc_hook 来 getshell
1 | create(7, 0x60) |
EXP
1 | from pwn import * |
- 本文作者: A1ex
- 本文链接: http://yoursite.com/2020/08/31/IO-FILE泄露libc地址/
- 版权声明: 本博客所有文章除特别声明外,均采用 MIT 许可协议。转载请注明出处!